﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

/// <summary>
/// 作者：爱捣腾的吴大爷
/// 链接：https://www.jianshu.com/p/22cbb5e9036a
/// 來源：简书
/// 简书著作权归作者所有，任何形式的转载都请联系作者获得授权并注明出处。}
/// </summary>
namespace OfficeHelper.Help
{
    /// <summary>
    /// 用于对网络上的计算机远程唤醒
    /// </summary>
    public class WakeOnLan
    {
        #region WakeOnLan

        /// <summary>
        /// 发送一个WOL魔术包到远程计算机
        /// </summary>
        /// <param name="macAddress">MAC地址</param>
        /// <param name="hostNameOrAddress">Host主机名或IP地址</param>
        /// <param name="subnetMask">子网掩码</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存时间</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, string hostNameOrAddress, string subnetMask, int udpPort = 9, short ttl = 128)
        {
            // 获取主机的IP地址
            var hostIPs = Dns.GetHostAddresses(hostNameOrAddress).Where(a => a.AddressFamily == AddressFamily.InterNetwork);

            foreach (var hostIP in hostIPs)
            {
                // 获取该主机的广播地址
                var broadcastAddress = GetBroadcast(hostIP.ToString(), subnetMask);
                WakeUp(macAddress, broadcastAddress, udpPort, ttl);
            }

        }

        /// <summary>
        /// 发送一个WOL魔术包到远程计算机
        /// </summary>
        /// <param name="macAddress">MAC地址</param>
        /// <param name="broadcastAddress">网络广播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存时间</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, string broadcastAddress = null, int udpPort = 9, short ttl = 128)
        {

            if (string.IsNullOrWhiteSpace(macAddress))
            {
                throw new ArgumentNullException("macAddress", "必须提供MAC地址！");
            }

            if (!string.IsNullOrWhiteSpace(broadcastAddress) && !Regex.IsMatch(broadcastAddress, @"(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)"))
            {
                throw new ArgumentNullException("broadcastAddress", "网络广播地址格式错误！");
            }

            // 获取MAC地址对应的字节数组
            var bytesMac = GetMac(macAddress);

            // 广播地址
            var broadcastIP = IPAddress.Broadcast;

            if (!string.IsNullOrWhiteSpace(broadcastAddress))
            {
                broadcastIP = IPAddress.Parse(broadcastAddress);
            }

            WakeUp(bytesMac, broadcastIP, udpPort, ttl);
        }

        /// <summary>
        /// 发送一个WOL魔术包到远程计算机
        /// </summary>
        /// <param name="macAddress">唤醒MAC地址</param>
        /// <param name="broadcastIPAddress">网络广播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存时间</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, IPAddress broadcastIPAddress = null, int udpPort = 9, short ttl = 128)
        {

            if (string.IsNullOrWhiteSpace(macAddress))
            {
                throw new ArgumentNullException("macAddress", "必须提供MAC地址！");
            }

            var bytesMac = GetMac(macAddress);

            WakeUp(bytesMac, broadcastIPAddress, udpPort, ttl);
        }

        /// <summary>
        /// 发送一个WOL魔术包到远程计算机
        /// </summary>
        /// <param name="bytesMac">MAC地址字节数组</param>
        /// <param name="broadcastIPAddress">网络广播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存时间</param>
        /// <remarks></remarks>
        public void WakeUp(byte[] bytesMac, IPAddress broadcastIPAddress = null, int udpPort = 9, short ttl = 128)
        {

            if (!(udpPort > 0 && udpPort < 65535))
            {
                throw new ArgumentNullException("udpPort", "端口范围错误，端口号的范围从0到65535！");
            }

            #region 构造魔术封包
            // 局域网唤醒魔术包包含一个6字节的头和目标的MAC地址6字节，重复16次。
            var wolPacket = new byte[17 * 6];

            var ms = new MemoryStream(wolPacket, true);

            // 写入6字节的0xFF头
            for (int i = 0; i < 6; i++)
            {
                ms.WriteByte(0xFF);
            }

            // 写MAC地址16次
            for (int i = 0; i < 16; i++)
            {
                ms.Write(bytesMac, 0, bytesMac.Length);
            }
            #endregion

            // 创建UDP客户端
            var udp = new UdpClient();

            // 广播地址
            var broadcast = broadcastIPAddress ?? IPAddress.Broadcast;
            // 设置udp连接的地址和端口
            udp.Connect(broadcast, udpPort);
            // 设置TTL
            udp.Ttl = ttl;
            // 发送魔法数据包
            udp.Send(wolPacket, wolPacket.Length);
        }

        /// <summary>
        /// 处理字符串的MAC地址
        /// </summary>
        /// <param name="mac">以空格，：，-，分隔的mac地址</param>
        /// <returns>mac地址的字节数组</returns>
        public byte[] GetMac(string mac)
        {
            // 地址格式判断并不严谨，以空格，：，-，分隔的mac地址，也可以是混用分隔符的地址。
            if (!Regex.IsMatch(mac, @"^([0-9a-fA-F]{2})(([\s:-][0-9a-fA-F]{2}){5})$"))
            {
                throw new ArgumentNullException("mac", "MAC地址格式错误！");
            }

            // 去除分隔符
            var mMac = mac.Replace(" ", "")
                .Replace(":", "")
                .Replace("-", "");

            byte[] bytesMac = new byte[6];

            for (int i = 0; i < 6; i++)
            {
                //bytesMac[i] = (byte)Int32.Parse(mMac.Substring((i * 2), 2), NumberStyles.HexNumber);
                // 将字符串转化为字节
                bytesMac[i] = Convert.ToByte(mMac.Substring((i * 2), 2), 16);
            }

            return bytesMac;
        }
        #endregion

        #region 计算地址

        /// <summary> 
        /// 获得广播地址 
        /// </summary> 
        /// <param name="ipAddress">IP地址</param> 
        /// <param name="subnetMask">子网掩码</param> 
        /// <returns>广播地址</returns> 
        public static IPAddress GetBroadcast(string ipAddress, string subnetMask)
        {
            return GetBroadcast(IPAddress.Parse(ipAddress), IPAddress.Parse(subnetMask));
        }

        /// <summary> 
        /// 获得广播地址 
        /// </summary> 
        /// <param name="ipAddress">IP地址</param> 
        /// <param name="subnetMask">子网掩码</param> 
        /// <returns>广播地址</returns> 
        public static IPAddress GetBroadcast(IPAddress ipAddress, IPAddress subnetMask)
        {

            byte[] ip = ipAddress.GetAddressBytes();
            byte[] sub = subnetMask.GetAddressBytes();

            // 广播地址=子网按位求反 再 或IP地址 
            for (int i = 0; i < ip.Length; i++)
            {
                ip[i] = (byte)((~sub[i]) | ip[i]);
            }

            return new IPAddress(ip);
        }

        #endregion

        #region Ping
        /// <summary>
        /// 默认超时时间
        /// </summary>
        private const int PING_TIMEOUT = 1000;

        /// <summary>
        /// 检测目标主机是否处于可访问的状态
        /// </summary>
        /// <param name="hostNameOrAddress">主机名称或IP地址</param>
        /// <returns></returns>
        public static bool IsComputerAccessible(string hostNameOrAddress)
        {
            return IsComputerAccessible(hostNameOrAddress, PING_TIMEOUT);
        }

        /// <summary>
        /// 检测目标主机是否处于可访问的状态
        /// </summary>
        /// <param name="hostNameOrAddress">主机名称或IP地址</param>
        /// <param name="timeout">超时时间</param>
        /// <returns></returns>
        public static bool IsComputerAccessible(string hostNameOrAddress, int timeout)
        {
            var pingSender = new Ping();
            var reply = pingSender.Send(hostNameOrAddress, timeout);
            // 这里只判断ping成功的情况，如果需要更详细的状态可以自行处理
            return reply.Status == IPStatus.Success;
        }
        #endregion

        #region Arp

        /// <summary>
        /// 本地方法
        /// </summary>
        internal static class NativeMethods
        {
            /// <summary>
            /// 发送arp封包
            /// </summary>
            /// <param name="DestIP">目标地址</param>
            /// <param name="SrcIP">发送者IP，可以为0</param>
            /// <param name="pMacAddr">返回的远端IP的Mac地址</param>
            /// <param name="PhyAddrLen">返回MAC地址长度</param>
            /// <returns></returns>
            [DllImport("iphlpapi.dll", ExactSpelling = true)]
            internal static extern int SendARP(int DestIP, int SrcIP, byte[] pMacAddr, ref uint PhyAddrLen);
        }

        /// <summary>
        /// 获取MAC地址
        /// </summary>
        /// <param name="ipAddress">IP地址</param>
        /// <returns></returns>
        public static string GetMACAddress(IPAddress ipAddress)
        {
            var addressBytes = ipAddress.GetAddressBytes();
            var address = BitConverter.ToInt32(addressBytes, 0);

            var macAddr = new byte[6];
            var macAddrLen = (uint)macAddr.Length;

            if (NativeMethods.SendARP(address, 0, macAddr, ref macAddrLen) != 0)
            {
                return null;
            }

            var macAddressString = new StringBuilder();

            for (int i = 0; i < macAddr.Length; i++)
            {
                if (macAddressString.Length > 0)
                {
                    macAddressString.Append(":");
                }
                macAddressString.AppendFormat("{0:x2}", macAddr[i]);
            }

            return macAddressString.ToString();
        }

        /// <summary>
        /// 获取MAC地址
        /// </summary>
        /// <param name="hostName">主机名称</param>
        /// <returns></returns>
        public static string GetMACAddress(string hostName)
        {

            IPAddress[] mIPAddress = null;
            try
            {
                mIPAddress = Dns.GetHostAddresses(hostName);
            }
            catch
            {

                return null;
            }

            if (mIPAddress.Length == 0)
            {
                return null;
            }

            // 为该主机找到第一个地址的IPV4地址
            #region .Net 2 方法
            /*
                IPAddress ipAddress = null;

                foreach (IPAddress ip in hostEntry.AddressList)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        ipAddress = ip;
                        break;
                    }
                }
                */
            #endregion

            // 如果在.net 3.5上运行，你可以用LINQ来做
            var ipAddress = mIPAddress.First(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            return GetMACAddress(ipAddress);

        }

        /// <summary>
        /// 获取MAC地址列表
        /// </summary>
        /// <param name="hostName">主机名称</param>
        /// <returns></returns>
        public static IList<string> GetMACAddressArrray(string hostName)
        {

            IPHostEntry mIPHostEntry = null;
            try
            {
                mIPHostEntry = Dns.GetHostEntry(hostName);
            }
            catch
            {
                return null;
            }

            if (mIPHostEntry.AddressList.Length == 0)
            {
                return null;
            }

            var ipAddressList = mIPHostEntry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            var macList = new List<string>();

            foreach (var ipAddress in ipAddressList)
            {
                macList.Add(GetMACAddress(ipAddress));
            }

            return macList;

        }

        #endregion
    }
}
   